﻿// -----------------------------------------------------------------------
// <copyright file="MemoryClientSessionRepository.cs" company="FastNET Services">
//     Copyright (c) 2008-2009. All Rights Reserved.
// </copyright>
// <license>
//     GPLv2
// </license>
// <warranty>
//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//     BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//     DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </warranty>
// -----------------------------------------------------------------------
#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using GateShift.Common.Repository;
using GateShift.Common.Repository.Interfaces.DataInterfaces.System;
using GateShift.Common.Repository.Interfaces.Repositories.System;
using GateShift.Servers.GameServer.CoreData.MemoryData.Data;

#endregion

namespace GateShift.Servers.GameServer.CoreData.MemoryData.Repositories
{
    /// <summary>
    /// Localized memory repository for client sessions
    /// </summary>
    internal class MemoryClientSessionRepository : IClientSessionRepository
    {
        #region Static Variables

        private static readonly Dictionary<Guid, IClientSessionData> _dataList;
        private static readonly object _dataLock;

        #endregion

        #region CTor/DTor

        /// <summary>
        /// Initializes static members of the MemoryClientSessionRepository class
        /// </summary>
        static MemoryClientSessionRepository()
        {
            _dataLock = new object();
            _dataList = new Dictionary<Guid, IClientSessionData>();
        }

        #endregion

        /// <summary>
        /// Retrieves all instances of IClientSessionData from the repository
        /// </summary>
        /// <returns>IEnumerable of the requested IClientSessionData</returns>
        public IList<IClientSessionData> Retrieve()
        {
            lock (_dataLock)
            {
                return (from client in _dataList select client.Value).ToList();
            }
        }

        /// <summary>
        /// Deletes the requested IClientSessionData
        /// </summary>
        /// <param name="Data">IClientSessionData to delete from the repository</param>
        /// <exception cref="ArgumentNullException">Thrown if data is null</exception>
        /// <exception cref="InvalidCastException">Thrown if the data is of the wrong type for this datasource</exception>
        /// <exception cref="InvalidOperationException">Thrown if the IClientSessionData is new or already deleted</exception>
        public void Delete(IClientSessionData Data)
        {
            if (Data == null)
            {
                throw new ArgumentNullException("Data");
            }

            if (!(Data is MemoryClientSession))
            {
                throw new InvalidCastException("Data must be of type MemoryClientSession");
            }

            if (Data.IsDeleted)
            {
                throw new InvalidOperationException("Data is already deleted");
            }

            if (Data.IsNew)
            {
                throw new InvalidOperationException("Data is new and cannot be deleted");
            }

            // We need to hit our list and delete the item
            lock (_dataLock)
            {
                if (_dataList.Remove(Data.ID))
                {
                    ((MemoryClientSession)Data).IsDeleted = true;
                }
            }
        }

        /// <summary>
        /// Saves the requested IClientSessionData
        /// </summary>
        /// <param name="Data">IClientSessionData to save to the repository</param>
        /// <exception cref="ArgumentNullException">Thrown if data is null</exception>
        /// <exception cref="InvalidCastException">Thrown if the data is of the wrong type for this datasource</exception>
        /// <exception cref="InvalidOperationException">Thrown if the data is deleted</exception>
        public void Save(IClientSessionData Data)
        {
            if (Data == null)
            {
                throw new ArgumentNullException("Data");
            }

            if (!(Data is MemoryClientSession))
            {
                throw new InvalidCastException("Data must be of type MemoryClientSession");
            }

            if (Data.IsDeleted)
            {
                throw new InvalidOperationException("Cannot save a deleted session");
            }

            // If we are not dirty then do not save
            if (!Data.IsDirty)
            {
                return;
            }

            lock (_dataLock)
            {
                if (_dataList.ContainsKey(Data.ID))
                {
                    _dataList[Data.ID] = Data;
                }
                else
                {
                    _dataList.Add(Data.ID, Data);
                }

                ((MemoryClientSession)Data).IsNew = false;
                ((MemoryClientSession)Data).IsDirty = false;
            }
        }

        /// <summary>
        /// Creates a new instance of IClientSessionData
        /// </summary>
        /// <returns>New Instance of IClientSessionData</returns>
        public IClientSessionData Create()
        {
            return new MemoryClientSession();
        }

        /// <summary>
        /// Retrieves the requested IClientSessionData by an Identity
        /// </summary>
        /// <param name="ID">Identity to retrieve the data by</param>
        /// <returns>IData instance or null if one was not found</returns>
        /// <exception cref="InvalidCastException">Thrown if the identity is of the wrong type for this datasource</exception>
        public IClientSessionData Retrieve(Identity ID)
        {
            if (!ID.CanFitInGuid())
            {
                throw new InvalidCastException("ID is not of type Guid");
            }

            lock (_dataLock)
            {
                return _dataList.ContainsKey(ID) ? _dataList[ID] : null;
            }
        }

        /// <summary>
        /// Clears all sessions from the database
        /// </summary>
        /// <returns>Boolean whether or not the clearing of all sessions was successful</returns>
        public bool ClearSessions()
        {
            lock (_dataLock)
            {
                _dataList.Clear();
                return true;
            }
        }

        /// <summary>
        /// Clears all sessions from the database for a specific server
        /// </summary>
        /// <param name="ServerID">ID of the server hosting the session</param>
        /// <returns>Boolean whether or not the clearing of all sessions was successful</returns>
        public bool ClearSessions(Guid ServerID)
        {
            lock (_dataLock)
            {
                foreach (Guid key in from client in _dataList where client.Value.ServerID == ServerID select client.Key)
                {
                    _dataList.Remove(key);
                }
            }

            return true;
        }

        /// <summary>
        /// Retrieves all sessions from the database that have expired
        /// </summary>
        /// <returns>IList of IClientSessionData of all matching sessions</returns>
        public IList<IClientSessionData> RetrieveExpiredSessions()
        {
            lock (_dataLock)
            {
                return (from client in _dataList
                        where client.Value.TokenExpirationDate.Subtract(DateTime.UtcNow).TotalMilliseconds < 0
                        select client.Value).ToList();
            }
        }

        /// <summary>
        /// Retrieves all sessions from the database that have expired for a specific server
        /// </summary>
        /// <param name="ServerID">ID of the server hosting the session</param>
        /// <returns>IList of IClientSessionData of all matching sessions</returns>
        public IList<IClientSessionData> RetrieveExpiredSessions(Guid ServerID)
        {
            lock (_dataLock)
            {
                return (from client in _dataList
                        where client.Value.TokenExpirationDate.Subtract(DateTime.UtcNow).TotalMilliseconds < 0
                            && client.Value.ServerID == ServerID
                        select client.Value).ToList();
            }
        }

        /// <summary>
        /// Expires a specific session from the database
        /// </summary>
        /// <param name="SessionID">ID of the session to expire</param>
        /// <returns>Boolean whether or not expiring of the session was successful</returns>
        public bool ExpireSession(Identity SessionID)
        {
            lock (_dataLock)
            {
                if (_dataList.ContainsKey(SessionID))
                {
                    _dataList[SessionID].TokenExpirationDate = DateTime.UtcNow;
                    return true;
                }

                return false;
            }
        }
    }
}
